package com.haizhi.crm.sys.auth.service.impl;

import com.alibaba.fastjson.JSON;
import com.haizhi.crm.common.constant.CrmSchema;
import com.haizhi.crm.common.constant.EsOperator;
import com.haizhi.crm.common.constant.SysStatus;
import com.haizhi.crm.common.exception.UnexpectedStatusException;
import com.haizhi.crm.common.file.ExcelUtils;
import com.haizhi.crm.common.file.FileUploadConfig;
import com.haizhi.crm.common.file.FileUtils;
import com.haizhi.crm.common.json.JSONUtils;
import com.haizhi.crm.common.log.GLog;
import com.haizhi.crm.common.log.LogFactory;
import com.haizhi.crm.common.model.Response;
import com.haizhi.crm.search.model.SearchQo;
import com.haizhi.crm.search.model.SearchResp;
import com.haizhi.crm.search.service.SearchService;
import com.haizhi.crm.sys.auth.dao.*;
import com.haizhi.crm.sys.auth.model.*;
import com.haizhi.crm.sys.auth.service.RelationPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * Created by yangyijun on 2018/5/22.
 */
@Service
@Transactional
public class RelationPermissionServiceImpl implements RelationPermissionService {

    private static final GLog LOG = LogFactory.getLogger(RelationPermissionServiceImpl.class);
    private static final String SUB_PATH = "/relationpermission/";
    private static final String FILE_SEPARATE = "/";

    @Autowired
    private UserDao userDao;
    @Autowired
    private UserRoleDao userRoleDao;
    @Autowired
    private ResourceDao resourceDao;
    @Autowired
    private SearchService searchService;
    @Autowired
    private RelationItemDao relationItemDao;
    @Autowired
    private RelationRoleDao relationRoleDao;
    @Autowired
    private RoleResourcePermissionDao roleResourcePermissionDao;
    @Autowired
    private FileUploadConfig fileUploadConfig;


    @Override
    public Response save(MultipartFile file, Long relationId, Long[] resourceIds) {
        Integer count = 0;
        try {
            count = relationItemDao.countByRelationId(relationId);
            if (count > 0) {
                throw new UnexpectedStatusException(SysStatus.HAD_RELATION_PERMISSION_ERROR);
            }
            List<RelationRoleDo> relationRoleDos = relationRoleDao.findByRelationId(relationId);
            RelationRoleDo relationRoleDo = relationRoleDos.get(0);
            count = roleResourcePermissionDao.findByRoleIds(new Long[]{relationRoleDo.getRoleId()});
            if (count > 0) {
                throw new UnexpectedStatusException(SysStatus.HAD_RELATION_PERMISSION_ERROR);
            } else {
                persist(file, relationId, resourceIds);
            }
            return Response.success();
        } catch (Exception e) {
            LOG.error("save relation permission error", e);
            if (count > 0) {
                throw new UnexpectedStatusException(SysStatus.HAD_RELATION_PERMISSION_ERROR);
            } else {
                throw new UnexpectedStatusException(SysStatus.SAVE_RELATION_PERMISSION_ERROR, e);
            }
        }
    }


    @Override
    public Response update(MultipartFile file, Long relationId, Long[] resourceIds) {
        try {
            del(file == null ? false : true, relationId);
            persist(file, relationId, resourceIds);
            return Response.success();
        } catch (Exception e) {
            LOG.error("update relation permission error", e);
            throw new UnexpectedStatusException(SysStatus.UPDATE_RELATION_PERMISSION_ERROR, e);
        }
    }

    @Override
    public Response delete(Long relationId) {
        try {
            if (CollectionUtils.isEmpty(resourceDao.findByRelationId(relationId))) {
                throw new UnexpectedStatusException(SysStatus.RELATION_NOT_EXISTS_PERMISSION_ERROR);
            }
            del(true, relationId);
            LOG.info("relation {0} permission deleted", relationId);
            return Response.success();
        } catch (UnexpectedStatusException e) {
            throw new UnexpectedStatusException(e.getStatus(), e, relationId);
        } catch (Exception e) {
            LOG.error("delete relation permission error", e);
            throw new UnexpectedStatusException(SysStatus.DELETE_RELATION_PERMISSION_ERROR, e);
        }
    }


    @Override
    public Response list() {
        try {
            List<RelationRoleVo> relationRoleVos = relationRoleDao.list();
            List<RelationResourceVo> relationResourceVos = new ArrayList<>();
            for (RelationRoleVo relationRoleVo : relationRoleVos) {
                relationResourceVos.add(buildRelationResource(relationRoleVo));
            }
            return Response.success(relationResourceVos);
        } catch (Exception e) {
            LOG.error("search relation permission list error", e);
            throw new UnexpectedStatusException(SysStatus.LIST_RELATION_PERMISSION_ERROR, e);
        }
    }

    @Override
    public Response detail(Long relationId) {
        try {
            List<RelationRoleVo> relationRoleVos = relationRoleDao.findBy(relationId);
            RelationResourceVo relationResourceVo = buildRelationResource(relationRoleVos.get(0));
            return Response.success(relationResourceVo);
        } catch (Exception e) {
            LOG.error("search relation permission detail error", e);
            throw new UnexpectedStatusException(SysStatus.DETAIL_RELATION_PERMISSION_ERROR, e);
        }
    }

    @Override
    public void download(HttpServletResponse response, String fileRelativePath) {
        String fileUploadPath = getFileUploadFullPath(fileRelativePath);
        FileUtils.download(response, fileUploadPath);
    }

    ///////////////////////
    // private functions
    ///////////////////////

    private void persist(MultipartFile file, Long relationId, Long[] resourceIds) throws Exception {
        if (null != file) {
            //1.upload relation excel file to server
            String fileName = file.getOriginalFilename();
            String relativePath = SUB_PATH + relationId;
            String uploadPath = getFileUploadPath(relativePath);
            FileUtils.upload(file, uploadPath);

            //2.read excel relation item import into table tr_relation_item
            Set<Long> userIds = readExcelAndImportMySQL(relationId, uploadPath + FILE_SEPARATE + fileName);

            //3.save role resources permission to table tr_role_resource_permission
            RelationRoleDo relationRoleDo = saveRoleResourcePermission(relationId, resourceIds);

            //4.save user role to table tr_user_role
            saveUserRole(userIds, relationRoleDo.getRoleId());

            //5.save relation excel file upload path to table tr_relation_role
            relationRoleDo.setUpdatedDt(new Date());
            relationRoleDo.setRelationFileUrl(relativePath + FILE_SEPARATE + fileName);
            relationRoleDo.setRelationFileName(fileName);
            relationRoleDao.save(relationRoleDo);
        } else {
            //save role resources permission to table tr_role_resource_permission
            saveRoleResourcePermission(relationId, resourceIds);
        }
    }

    private void saveUserRole(Set<Long> userIds, Long roleId) {
        List<UserRoleDo> roleDos = new ArrayList<>();
        for (Long userId : userIds) {
            List<UserRoleDo> userRoleDos = userRoleDao.findByUserIdAndRoleId(userId, roleId);
            if (CollectionUtils.isEmpty(userRoleDos)) {
                UserRoleDo userRoleDo = new UserRoleDo();
                userRoleDo.setRoleId(roleId);
                userRoleDo.setUserId(userId);
                roleDos.add(userRoleDo);
            }
        }
        userRoleDao.save(roleDos);
    }

    private void del(boolean enableDelete, Long relationId) {
        //3.find role_id by relationId from tr_relation_role and then
        List<RelationRoleDo> relationRoleDos = relationRoleDao.findByRelationId(relationId);
        RelationRoleDo relationRoleDo = relationRoleDos.get(0);
        // delete from tr_role_resource_permission where role_id in it
        Long roleId = relationRoleDo.getRoleId();
        roleResourcePermissionDao.deleteByRoleId(roleId);
        if (!enableDelete) {
            return;
        }
        //1.delete from tr_relation_item where relation_id = relationId
        relationItemDao.deleteByRelationId(relationId);

        //2.update table tr_relation_role set relation_file_url=''

        String relationFileUrl = relationRoleDo.getRelationFileUrl();
        relationRoleDo.setRelationFileUrl(null);
        relationRoleDo.setRelationFileName(null);
        relationRoleDao.save(relationRoleDo);

        //4.delete already uploaded excel relation file from server
        FileUtils.delete(getFileUploadFullPath(relationFileUrl));
    }

    private Set<Long> readExcelAndImportMySQL(Long relationId, String uploadPath) throws Exception {
        String[] fields = new String[]{"userNo", "entityKey", "startDt", "endDt"};
        List<RelationItemDo> relationItemDos = ExcelUtils.readExcel(RelationItemDo.class, uploadPath, fields);
        Set<String> companyNames = new HashSet<>();
        Set<String> userNos = new HashSet<>();
        for (RelationItemDo relationItemDo : relationItemDos) {
            relationItemDo.setRelationId(relationId);
            companyNames.add(relationItemDo.getEntityKey());
            userNos.add(relationItemDo.getUserNo());
        }
        Set<Long> userIds = filterInvalidRelationByUserNos(relationItemDos, userNos);
        filterInvalidRelationByCompanyNames(relationItemDos, companyNames);
        relationItemDao.save(relationItemDos);
        return userIds;
    }

    private void filterInvalidRelationByCompanyNames(List<RelationItemDo> relationItemDos, Set<String> companyNames) {
        Map<String, String> nameKeyMap = searchCompanysBy(companyNames);
        List<RelationItemDo> entityKeyNull = new ArrayList<>();
        for (RelationItemDo relationItemDo : relationItemDos) {
            String entityKey = nameKeyMap.getOrDefault(relationItemDo.getEntityKey(), null);
            if (null == entityKey) {
                entityKeyNull.add(relationItemDo);
                continue;
            }
            relationItemDo.setEntityKey(entityKey);
        }
        relationItemDos.removeAll(entityKeyNull);
    }

    private Set<Long> filterInvalidRelationByUserNos(List<RelationItemDo> relationItemDos, Set<String> userNos) {
        List<UserVo> userVos = userDao.findByUserNos(userNos.toArray(new String[userNos.size()]));
        Map<String, Long> userNoIdMap = new HashMap<>();
        for (UserVo userVo : userVos) {
            userNoIdMap.put(userVo.getUserNo(), userVo.getId());
        }
        List<RelationItemDo> idNull = new ArrayList<>();
        for (RelationItemDo relationItemDo : relationItemDos) {
            Long userId = userNoIdMap.get(relationItemDo.getUserNo());
            if (null == userId) {
                idNull.add(relationItemDo);
                continue;
            }
            relationItemDo.setUserId(userId);
        }
        relationItemDos.removeAll(idNull);
        Set<Long> userIds = new HashSet<>();
        userIds.addAll(userNoIdMap.values());
        return userIds;
    }

    private RelationRoleDo saveRoleResourcePermission(Long relationId, Long[] resourceIds) {
        RelationRoleDo relationRoleDo = relationRoleDao.findByRelationId(relationId).get(0);
        Long roleId = relationRoleDo.getRoleId();
        List<RoleResourcePermissionDo> rrpDos = new ArrayList<>();
        buildRoleResourcePermission(resourceIds, roleId, rrpDos, "true" );

        List<Long> allResourceIds = resourceDao.findByParentId(11l);
        allResourceIds.removeAll(Arrays.asList(resourceIds));
        buildRoleResourcePermission(allResourceIds.toArray(new Long[allResourceIds.size()]), roleId, rrpDos, "false" );

        roleResourcePermissionDao.save(rrpDos);
        return relationRoleDo;
    }

    private void buildRoleResourcePermission(Long[] resourceIds, Long roleId, List<RoleResourcePermissionDo> rrpDos, String checked) {
        for (Long resourceId : resourceIds) {
            RoleResourcePermissionDo rrpDo = new RoleResourcePermissionDo();
            rrpDo.setRoleId(roleId);
            rrpDo.setResourceId(resourceId);
            rrpDo.setPermissionId(1l);
            rrpDo.setChecked(checked);
            rrpDos.add(rrpDo);
        }
    }

    private RelationResourceVo buildRelationResource(RelationRoleVo relationRoleVo) {
        RelationResourceVo rrVo = new RelationResourceVo();
        rrVo.setRelationId(relationRoleVo.getRelationId());
        rrVo.setRelationName(relationRoleVo.getRelationName());
        rrVo.setRelationFileName(relationRoleVo.getRelationFileName());
        rrVo.setRelationFileUrl(relationRoleVo.getRelationFileUrl());
        rrVo.setUpdateDt(relationRoleVo.getUpdateDt());
        List<ResourceVo> resourceVos = ResourceVo.buildResourceTree(resourceDao.findByRelationId(relationRoleVo.getRelationId()));
        rrVo.setResourceVos(resourceVos);
        return rrVo;
    }

    private Map<String, String> searchCompanysBy(Set<String> companyNames) {
        SearchQo searchQo = buildSearchQo(companyNames);
        SearchResp searchResp = searchService.search(searchQo).getSearchResp();
        String jsonData = JSON.toJSONString(searchResp.getData());
        List<Map<String, Object>> companys = JSONUtils.jsonToListMap(jsonData);
        Map<String, String> nameKeyMap = new HashMap<>();
        for (Map<String, Object> map : companys) {
            nameKeyMap.put(map.get("name" ).toString(), map.get("object_key" ).toString());
        }
        return nameKeyMap;
    }

    private SearchQo buildSearchQo(Set<String> companyNames) {
        SearchQo searchQo = new SearchQo();
        searchQo.setDomain(CrmSchema.CRM);
        searchQo.setGraph(CrmSchema.CRM);
        Set<String> schemas = new HashSet<>();
        schemas.add(CrmSchema.COMPANY);
        searchQo.setSchemas(schemas);

        List<Map<String, Object>> filters = new ArrayList<>();
        Map<String, Object> companyNameFilter = new HashMap<>();
        companyNameFilter.put("type", EsOperator.TERM);
        companyNameFilter.put("field", "name" );
        companyNameFilter.put("values", companyNames.toArray());
        filters.add(companyNameFilter);
        searchQo.setFilter(filters);
        return searchQo;
    }

    private String getFileUploadPath(String relativePath) {
        return fileUploadConfig.getUploadRootPath() + relativePath;
    }

    private String getFileUploadFullPath(String relativePathFileName) {
        return fileUploadConfig.getUploadRootPath() + relativePathFileName;
    }
}
